#!/usr/bin/perl

#Copyright (c) 2005 Apple Computer, Inc.  All Rights Reserved.
#
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

=pod

=head1 SYNOPSIS

report -- Report on the results of a buildfink run

=head1 USAGE

	report [--comments FILE] [--catdescs FILE] --outdir DIR --finkdir DIR

=head1 OPTIONS

=over 4

=item *

B<comments>: Path to a file containing comments on the run.  The contents of this file will
be inserted at the top of the report.

=item *

B<catdescs>: Path to a file mapping category names to category descriptions.  File
should contain one mapping per line, like so:

	somecat: Some category of test results.

These categories should match the filter names in the filter used by the C<analyze>
script.

=item *

B<outdir>: Path to the 'out' subdirectory from the C<buildfink> run.  C<analyze> must
already have been performed for this run.

=item *

B<finkdir>: Path to the fink root directory, e.g. '/sw'

=back

=head1 OUTPUT

A bunch of HTML files will be produced in the C<outdir> directory; a textual version
of the report will also be produced in that directory.

=cut

use strict;
use warnings;
use List::Util qw(sum);
use Getopt::Long;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use FinkLib;

my %opts;
my $opts_ok = GetOptions(
			 "comments=s" => \$opts{comments},
			 "catdescs=s" => \$opts{catdescs},
			 "outdir=s" => \$opts{outdir},
			 "finkdir=s" => \$opts{finkdir},
			);
if ($opts{comments} and not -f $opts{comments}) {
  warn "The specified comments file does not exist.\n";
  $opts_ok = 0;
}
if ($opts{catdescs} and not -f $opts{catdescs}) {
  warn "The specified category descriptions file does not exist.\n";
  $opts_ok = 0;
}
if (!$opts{outdir}) {
  warn "You must specify an output directory.\n";
  $opts_ok = 0;
} elsif (not -d $opts{outdir}) {
  warn "The specified output directory does not exist.\n";
  $opts_ok = 0;
} elsif (not -f "$opts{outdir}/successes") {
  warn "You do not appear to have run 'analyze' on this buildfink run, but proceeding anyway...\n";
}
if (!$opts{finkdir}) {
  warn "You must specify a Fink directory.\n";
  $opts_ok = 0;
} elsif (not -f "$opts{finkdir}/etc/fink.conf") {
  warn "The specified Fink directory does not appear to be valid.\n";
  $opts_ok = 0;
}

if (!$opts_ok) {
  die "See 'perldoc $0' for more information.\n";
}


FinkLib::initFink($opts{finkdir});
chdir($opts{outdir}) or die "Couldn't change to output directory: $!\n";

our %catdescs;
if ($opts{catdescs}) {
  open(CATDESCS, $opts{catdescs}) or die "Couldn't open category description file: $!\n";
  my $lineno = 1;
  while (<CATDESCS>) {
    chomp;
    /^(.*?):\s*(.*)/ or die "Line $lineno in category description file is malformed.\n";
    $catdescs{$1} = $2;
    $lineno++;
  } 
  close CATDESCS;
}


# This function does two things at once.  It reads the output
# of "analyze" to map packages to categories, and it transforms
# the logs into HTML, linking each package to its build log.
# It returns a list of packages in the filed defined by this
# particular category.
sub dologs {
  my($file, $depth) = @_;
  my @ret;

  open(IN, $file) or die "Couldn't open $file: $!\n";
  open(OUT, ">", "$file.html") or die "Couldn't open $file.html: $!\n";

  my $back = "../" x $depth;
  $back .= "logs/";
  while (<IN>) {
    s/^(\S*)\.log\s+(.+)/<a href="$back$1.log">$1<\/a> ($2)<br>/
      or
	s/^(\S*)\.log.*/<a href="$back$1.log">$1<\/a><br>/;;
    print OUT $_;
    push @ret, $1;
  }

  close OUT;
  close IN;

  return @ret;
}


sub countlines {
  my $file = shift;
  my $lines = 0;

  open(FILE, $file) or die "Couldn't open $file: $!\n";
  $lines++ while(<FILE>);
  close FILE;

  return $lines;
}

sub countdir {
  my($dir, $depth, $catmap) = @_;
  my @ret;

  opendir(DIR, $dir) or die "Couldn't opendir $dir: $!\n";
  my @files = grep {$_ ne "." and $_ ne ".." and $_ !~ /\.(html|txt|xml)$/} readdir(DIR);
  closedir(DIR);

  foreach my $file (@files) {
    if (-d "$dir/$file") {
      my @subs = countdir("$dir/$file", $depth+1, $catmap);
      push @ret, [$dir, $file, sum(map {$_->[2]} @subs), \@subs];
    } else {
      push @ret, [$dir, $file, countlines("$dir/$file")];
      my @catpkgs = dologs("$dir/$file", $depth);			
      $catmap->{$_} = "$dir/$file" foreach @catpkgs;
    }
  }

  return sort { $b->[2] <=> $a->[2] } @ret;
}


sub printrep {
  my($txtprefix, @items) = @_;

  print HTML "<ul>\n";

  foreach my $item (@items) {
    my $desc = "";
    $desc = ": ".$catdescs{$item->[1]} if $catdescs{$item->[1]};

    printf TXT "%s%4d %s%s\n", $txtprefix, $item->[2], $item->[1], $desc;

    my $html_space = sprintf("%4d", $item->[2]);
    $html_space =~ s/ /&nbsp;/g;

    if ($item->[3]) {
      printf HTML "<li>%s %s%s", $html_space, $item->[1], $desc;
      printrep("$txtprefix\t", @{$item->[3]});
      print HTML "</li>\n";
    } else {
      printf HTML "<li>%s <a href=\"%s/%s.html\">%s</a>%s</li>\n", $html_space, $item->[0], $item->[1], $item->[1], $desc;
    }
  }

  print HTML "</ul>\n";
}


# First, make the main report index.

my $comment = "";
if ($opts{comments}) {
  open(COMMENTS, $opts{comments}) or die "Couldn't open comment file: $!\n";
  $comment = join("", <COMMENTS>);
  close COMMENTS;
}

open(TXT, ">report.txt") or die "Couldn't open report.txt: $!\n";
open(HTML, ">report.html") or die "Couldn't open report.html: $!\n";

print TXT <<TXTHEAD;
$comment

TXTHEAD

print HTML <<HTMHEAD;
<html>
<head><title>Fink Build Report</title></head>
<body>
<h1>Fink Build Report</h1>
<p>You can view the <a href="filters.xml">failure category definitions</a>, the
<a href="../log.txt">build log</a>, or the list of results sorted by
<a href="pkgindex.html">package</a> or <a href="maintindex.html">maintainer</a>.</p>
<p><a href="../search.html">File listings (dpkg-deb -c output) for successful packages can also be searched</a>.</p>
<p>$comment</p>
HTMHEAD

my %catmap;
printrep("", countdir(".", 1, \%catmap));

print TXT <<TXTFOOT;
TXTFOOT

print HTML <<HTMFOOT;
</body></html>
HTMFOOT


# Now, produce the per-package index.

open(PKGIDX, ">", "pkgindex.html") or die "Couldn't open pkgindex: $!\n";

print PKGIDX <<HTMHEAD;
<html>
<head><title>Fink Build Report: Results By Package</title></head>
<body>
<h1>Fink Build Report: Results By Package</h1>
<p>You can view the <a href="filters.xml">failure category definitions</a>, the
<a href="../log.txt">build log</a>, or the list of results sorted by
<a href="maintindex.html">maintainer</a>.</p>
<p><a href="../search.html">File listings (dpkg-deb -c output) for successful packages can also be searched</a>.</p>
<p>$comment</p>
HTMHEAD

foreach my $pkg (sort keys %catmap) {
  my $result = $catmap{$pkg};
  print PKGIDX "<a href=\"../logs/$pkg.log\">$pkg</a>: <a href=\"$result.html\">$result</a><br>\n";
}

print PKGIDX "</body></html>\n";
close(PKGIDX);


# And the per-maintainer index.

my %maints = FinkLib::sortPackagesByMaintainer(keys %catmap);

open(MAINTIDX, ">", "maintindex.html") or die "Couldn't open maintindex: $!\n";

print MAINTIDX <<HTMHEAD;
<html>
<head><title>Fink Build Report: Results By Maintainer</title></head>
<body>
<h1>Fink Build Report: Results By Maintainer</h1>
<p>You can view the <a href="filters.xml">failure category definitions</a>, the
<a href="../log.txt">build log</a>, or the list of results sorted by
<a href="pkgindex.html">package</a>.</p>
<p><a href="../search.html">File listings (dpkg-deb -c output) for successful packages can also be searched</a>.</p>
<p>$comment</p>
HTMHEAD

foreach my $maint (sort keys %maints) {
  my $maintname = FinkLib::maintName($maint);
  if (!$maintname) {
    $maintname = FinkLib::maintEmail($maint);
    $maintname =~ s/\@/ _at_ /;
  }

  print MAINTIDX "<li><b>$maintname</b><ul>\n";
  foreach my $pkg (sort @{$maints{$maint}}) {
    my $result = $catmap{$pkg};
    print MAINTIDX "<li><a href=\"../logs/$pkg.log\">$pkg</a>: <a href=\"$result\">$result</a></li>\n";
  }
  print MAINTIDX "</ul></li>";
}
print MAINTIDX "</ul></body></html>\n";

close(MAINTIDX);
